﻿Imports System.Text.RegularExpressions

''' <summary>
''' Represents an analyzed line of text entered into the console for processing and its associated result.
''' </summary>
''' <remarks></remarks>
Public Class CommandLine

    ''' <summary>
    ''' Splits the ArgumentString into an array of strings using the specified delimiter.  This method supports values containing the delimiter when
    ''' the value is wrapped in doule-qoutes.
    ''' </summary>
    ''' <param name="argumentString">The source string containing the arugments to parse.</param>
    ''' <param name="delimiters">The delimiting characer(s) seperating each argument.</param>
    ''' <returns>An array of strings containing the parsed arguments.</returns>
    ''' <remarks></remarks>
    Public Shared Function ParseArgumentString(argumentString As String, ParamArray delimiters() As Char) As String()
        Dim result() As String
        Using reader As New System.IO.StringReader(argumentString.Trim(delimiters))
            Dim tfp As New Microsoft.VisualBasic.FileIO.TextFieldParser(reader)
            tfp.SetDelimiters(delimiters)
            tfp.HasFieldsEnclosedInQuotes = True
            result = tfp.ReadFields
            tfp.Close()
        End Using
        Return result
    End Function

    Protected Friend _IsProcessing As Boolean

    Protected Friend _ArgumentString As String = String.Empty
    ''' <summary>
    ''' Gets the entire argument string parsed from the command line. This is any text appearing after a parsed command, or
    ''' an empty string if no command was parsed.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ArgumentString As String
        Get
            Return _ArgumentString
        End Get
    End Property

    Private _CommandName As String = String.Empty
    ''' <summary>
    ''' Gets or sets the command associated with the command line, minus any interpreter command token. Returns an empty string if
    ''' no command name was parsed, and none was supplied during processing. This property can only be set while the command line
    ''' is being processed (during the interpreter's ProcessCommandLine event handler), otherwise an InvalidOperationException will occur.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property CommandName As String
        Get
            Return _CommandName
        End Get
        Set(value As String)
            If _IsProcessing Then
                _CommandName = value
            Else
                Throw New InvalidOperationException("The CommandName can only be set while the CommandLine is being processed.")
            End If
        End Set
    End Property

    Protected Friend _OriginalCommandString As String = String.Empty
    ''' <summary>
    ''' Gets the original command string parsed from the command line, if any. Includes any interpreter command token.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property OriginalCommandString As String
        Get
            Return _OriginalCommandString
        End Get
    End Property

    Private _Result As CommandResult
    ''' <summary>
    ''' Gets the result associated with this command line.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Result As CommandResult
        Get
            Return _Result
        End Get
    End Property

    Private _Text As String
    ''' <summary>
    ''' Gets the original input text of the command line.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Text As String
        Get
            Return _Text
        End Get
    End Property

    Private _Words() As CommandLineWord
    ''' <summary>
    ''' Gets the array of CommandLineWords parsed from this command line using the interpreter's WordMatchPattern.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Words() As CommandLineWord()
        Get
            Return _Words
        End Get
    End Property

    ''' <summary>
    ''' Gets a List of CommandLineWords from the specified string using the interpreter's WordMatchPattern.
    ''' </summary>
    ''' <param name="textLine"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetWords(textLine As String) As List(Of CommandLineWord)
        Dim pattern As New Regex(CommandInterpreter.WordMatchPattern)
        Dim words As New List(Of CommandLineWord)
        Dim matches As MatchCollection = pattern.Matches(textLine)
        For index As Integer = 0 To matches.Count - 1
            If matches(index).Length > 0 Then
                words.Add(New CommandLineWord(matches(index), index))
            End If
        Next
        Return words
    End Function

    Protected Friend Sub New(textLine As String)
        If textLine Is Nothing Then textLine = String.Empty
        _Text = textLine
        Dim words As List(Of CommandLineWord) = GetWords(textLine)
        _Words = words.ToArray
        If words.Count > 0 Then
            _OriginalCommandString = words(0).Word.Trim
            If CommandInterpreter.CommandIndentifierToken.Length > 0 Then
                If _OriginalCommandString.StartsWith(CommandInterpreter.CommandIndentifierToken) Then
                    _CommandName = _OriginalCommandString.Substring(CommandInterpreter.CommandIndentifierToken.Length)
                Else
                    _CommandName = String.Empty
                End If
            Else
                _CommandName = _OriginalCommandString
            End If
            Dim startIndex As Integer = CommandInterpreter.CommandIndentifierToken.Length + _OriginalCommandString.Length
            If startIndex < textLine.Length Then
                _ArgumentString = textLine.Substring(startIndex)
            End If
        End If
        If Not CommandInterpreter.CaseSensitive Then _CommandName = _CommandName.ToLower
        _Result = New CommandResult(Me)
    End Sub

    ''' <summary>
    ''' Adds an event entry to this command line's event log.
    ''' </summary>
    ''' <param name="level"></param>
    ''' <param name="source"></param>
    ''' <param name="className"></param>
    ''' <param name="code"></param>
    ''' <param name="message"></param>
    ''' <remarks></remarks>
    Public Sub LogEvent(level As Integer, source As String, className As String, code As Integer, message As String)
        If _IsProcessing Then
            Dim entry As New LogEntry(level, source, className, code, message)
            _Result._Log.Add(entry)
        Else
            Throw New InvalidOperationException("LogEvent can only be called while the CommandLine is being processed.")
        End If
    End Sub

    ''' <summary>
    ''' Splits the ArgumentString into an array of strings using the specified delimiter.  This method supports values containing the delimiter when
    ''' the value is wrapped in doule-qoutes.
    ''' </summary>
    ''' <param name="delimiters"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ParseArguments(ParamArray delimiters() As Char) As String()
        Return ParseArgumentString(_ArgumentString, delimiters)
    End Function

    ''' <summary>
    ''' Sets the result message for the command line.
    ''' </summary>
    ''' <param name="message"></param>
    ''' <remarks></remarks>
    Public Sub SetResultMessage(message As String)
        SetResultMessage(message, False)
    End Sub

    ''' <summary>
    ''' Sets the result or error message for the command line.
    ''' </summary>
    ''' <param name="message"></param>
    ''' <param name="isError"></param>
    ''' <remarks></remarks>
    Public Sub SetResultMessage(message As String, isError As Boolean)
        If _IsProcessing Then
            If message Is Nothing Then message = String.Empty
            If isError Then
                _Result._ErrorText = message
            Else
                _Result._ResultText = message
            End If
        Else
            Throw New InvalidOperationException("SetResultMessage can only be called while the CommandLine is being processed.")
        End If
    End Sub
End Class

